
from EngineDC import *

#import SafeEval

the_engine = Engine()

# Possible input commands
#   Player commands (e.g., up, fire, jetpack)
#   Game commands (e.g., quit, display menu, pause)

#createInputCommand("player.up", self.player.giveCommand)
#createInputCommand("select_button_down")
#createInputCommand("action_button_up")

#bindInputCommandToKey("player.up", pygame.K_w)


def curry(func,*args,**kwargs):
    def curried(*moreargs,**morekwargs):
        morekwargs.update(kwargs)
        return func(*(moreargs+args),**morekwargs)
    return curried

class Player(object):
    def __init__(self,name):
        self.name = name
        self.entity = None

the_player = Player('someone')
the_player.selected_models = []



class Game(object):

    def __init__(self):
        # Create game engine instance
        #self.engine = Engine()

        # TODO: Remove this crud
        #the_engine.addEventListener('render', self.render)
        #the_engine.addEventListener('tick', self.tick)
        
        # Engine settings
        the_engine.tickRate = 60
        the_engine.gravity = 0.0
        the_engine.worldMinX = -20.0
        the_engine.worldMinY = -20.0
        the_engine.worldMaxX = 20.0
        the_engine.worldMaxY = 20.0
        # TODO: Work out why turning this on makes the physics go haywire
        the_engine.usePhysicsSleeping = False
        the_engine.windowWidth = 800
        the_engine.windowHeight = 600
        the_engine.forceResolution = True
        the_engine.fullscreen = False
        #the_engine.physicsIterations = 10
        the_engine.velocity_iterations = 10
        the_engine.position_iterations = 8
        the_engine.init()

        the_engine.renderer.enableParallax(True, True)

        # Set up the render layers
        the_engine.renderer.addLayer("background", 0)
        the_engine.renderer.addLayer("distant", 0.5)
        the_engine.renderer.addLayer("behind", 1)
        the_engine.renderer.addLayer("play", 1)
        the_engine.renderer.addLayer("infront", 1)
        the_engine.renderer.addLayer("foreground", 2.0)
        the_engine.renderer.addLayer("GUI", 0)

        the_engine.renderer.disableMotionBlur()

        # Set up the camera
        the_engine.renderer.camera.setMaxZoomRate(0.01)
        the_engine.renderer.camera.setZoomAccel(0.02)
        the_engine.renderer.camera.zoomTo(0.1)

        # TODO: Remove this and replace with the level's solid_geometry
        #groundShapeDef = b2PolygonDef()
        #groundShapeDef.SetAsBox(40.0, 0.1, b2Vec2(0.0, -3.0), 0.0)
        #the_engine.physics_world.GetGroundBody().CreateShape(groundShapeDef)

        world = the_engine.world

        #the_engine.renderer.camera.followBody(self.playerChar.referenceBody)

        #self.soldiers = []
        # Create two teams of Soldiers
        count = 20
        for i in range(count):
            #soldier = 
            world.createEntity(Soldier, None, 0, \
                               b2Vec2(-5.0 + 0.7*(i%int(math.sqrt(count))), \
                                      -5.0 + 0.7*(i/int(math.sqrt(count)))))
        for i in range(count):
            world.createEntity(Soldier, None, 1, \
                               b2Vec2(5.0 + 0.7*(i%int(math.sqrt(count))), \
                                      -5.0 + 0.7*(i/int(math.sqrt(count)))))

        #level = world.createEntity(Level, "level")
        #level.load("Level1")

        self.selected_units = []
        self.selected_models = []

        #the_engine.run() # not here I think

    def quit(self):
        the_engine.running = False

class Command(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

class Unit(CompositeBody, Commandable):
    def __init__(self, engine, name, team):
        CompositeBody.__init__(self, engine, name)
        Commandable.__init__(self,['move','attack'])
        self.health = 100
        self.team = team
        if self.team == 0:
            self.color = (1, 0.5, 0.5, 1)
        elif self.team == 1:
            self.color = (0.5, 0.5, 1.0, 1)
        #self.experience = 0

    def isAllyOf(self, other_unit):
        return self.team == other_unit.team
    def isAllyOfAnyOf(self, other_units):
        for other_unit in other_units:
            if self.isAllyOf(other_unit):
                return True
        return False
    def ifAllyOfAllOf(self, other_units):
        for other_unit in other_units:
            if not self.isAllyOf(other_unit):
                return False
        return True

    def applyDamage(self, damage):
        self.health -= damage
        if self.health <= 0:
            self.die()

    def die(self):
        print "Unit", self.name, "has died."

class Soldier(Unit):
    def __init__(self, engine, name, team, pos):
        Unit.__init__(self, engine, name, team)
        self.addBody('body', self.engine.bodyLoader. \
                     createCircleBody(pos, radius=0.5, density=1), \
                     make_reference_body = True)
        #self.ref_body = self.bodies['body'] = self.engine.bodyLoader. \
        #                      createCircleBody(pos, radius=0.5, density=1)
        self.models['body'] = self.engine.renderer. \
                     createModel(layer="play", \
                                 mesh="soldier", \
                                 texture="soldier_standing", \
                                 influences=[(self.ref_body.GetXForm(), 1.0)], \
                                 local_xform=b2XForm_identity, \
                                 color=self.color)
        
        self.command = None
        
        #self.max_speed = 0.1
        self.move_strength = 3.0
        self.move_target = self.ref_body.GetPosition()

        self.attack_range = 8.0
        self.attack_damage = 1.0

        @self.command_rules.rule('givemove')
        def giveMove(self,pos):
            self.move_target = b2Vec2(pos[0], pos[1])
            if self.has_command['attack']:
                between = self.attack_target.getPos() - self.getPos()
                dist2 = between.LengthSquared()
                if dist2 > self.attack_range * self.attack_range: self.has_command['attack']=False
        @self.command_rules.rule('onmove')
        def onMove(self):                
            heading = self.move_target - self.getPos()
            heading.Normalize()
            self.applyCenteredForce(self.move_strength * heading)
        @self.command_rules.rule('giveattack')
        def giveAttack(self,target):
            self.attack_target = target
            self.has_command['move'] = False
        @self.command_rules.rule('onattack')
        def onAttack(self):
            between = self.attack_target.getPos() - self.getPos()
            dist2 = between.LengthSquared()
            # If out of range, move in closer
            if dist2 > self.attack_range * self.attack_range and not self.has_command['move']:
                heading = between / math.sqrt(dist2)
                self.applyCenteredForce(self.move_strength * heading)
            else:
                # Turn to face the enemy
                angle = -math.atan2(between.x, between.y)
                self.setXForm(b2XForm(self.getPos(), b2Mat22(angle)))
                #print "Unit", self.name, "fires at", self.attack_target.name
                #self.attack_target.applyDamage(self.attack_damage)


    def giveCommand(self, command):
        #print 'command',command.name,command.__dict__
        Commandable.giveCommand(self,command=command.name,value=True,**command.__dict__)
    
    def update(self, timeStep):
        # Turn the body to face the direction of motion
        if self.getLinearVelocity().Length() > 0.0:
            vel = self.getLinearVelocity()
            angle = -math.atan2(vel.x, vel.y)
            self.setXForm(b2XForm(self.getPos(), b2Mat22(angle)))
            #self.applyTorque(angle - self.getAngle())
            #self.applyDampingTorque(0.1)

        self.applyDampingForce(1.0)
        
        # Obey commands
        self.updateCommands()
        '''
        if self.command == 'move':
            heading = self.move_target - self.getPos()
            heading.Normalize()
            self.applyCenteredForce(self.move_strength * heading)
        elif self.command == 'attack':
            between = self.attack_target.getPos() - self.getPos()
            dist2 = between.LengthSquared()
            # If out of range, move in closer
            if dist2 > self.attack_range * self.attack_range:
                heading = between / math.sqrt(dist2)
                self.applyCenteredForce(self.move_strength * heading)
            else:
                # Turn to face the enemy
                angle = -math.atan2(between.x, between.y)
                self.setXForm(b2XForm(self.getPos(), b2Mat22(angle)))
                #print "Unit", self.name, "fires at", self.attack_target.name
                #self.attack_target.applyDamage(self.attack_damage)
        '''        

#setTrace()

def select(tf,pos):
    world_pos = the_engine.convertPxlsToWorldCoords(pos)
    if tf:
        the_player.select_start_pos = world_pos
    else:
        x1, y1 = the_player.select_start_pos
        x2, y2 = world_pos
        the_player.selected_units = the_engine.queryEntitiesInRect(x1, y1, \
                                                              x2, y2)
        for model in the_player.selected_models:
            the_engine.renderer.destroyModel(model)
        the_player.selected_models = []
        for unit in the_player.selected_units:
            # TODO: This is pretty awful!
            the_player.selected_models.append( \
                the_engine.renderer.createModel(layer="infront", \
                        mesh="soldier", \
                        texture=None, \
                        influences=[(unit.getXForm(), 1.0)], \
                        local_xform=b2XForm_identity, \
                        color=(0, 1, 0, 0.3))
                )

def order(tf,pos):
    world_pos = the_engine.convertPxlsToWorldCoords(pos)
    if len(the_player.selected_units) > 0:
        hit_units = the_engine.queryEntitiesAtPoint(world_pos[0], \
                                                    world_pos[1])
        if len(hit_units) == 1:
            hit_unit = hit_units[0]
            if hit_unit.isAllyOfAnyOf(the_player.selected_units):
                for unit in the_player.selected_units:
                    unit.giveCommand(Command(name='move', \
                                             pos=hit_unit.getPos().tuple()))
            else:
                for unit in the_player.selected_units:
                    unit.giveCommand(Command(name='attack', \
                                             target=hit_unit))
                    print "Unit", unit.name, "ordered to attack unit", hit_unit.name
        elif len(hit_units) == 0:
            for unit in the_player.selected_units:
                unit.giveCommand(Command(name='move', \
                                         pos=world_pos))
        else:
            raise Exception("What the? Mouse click hit multiple Units!")


game = Game()

gamerules = the_engine.engine_event_rules
gamerules.bindings = the_engine.keyboard_bindings
gamerules.mbindings = the_engine.mouse_bindings
    
@gamerules.eventMaker
def commandGiver(start,command):
    the_player.entity.giveCommand(command, start)

@gamerules.rule(pygame.KEYDOWN)
def handleInput(event):
    try: gamerules.bindings[event.key](False)
    except KeyError: print 'Key',chr(event.key),'not bound'
    
@gamerules.rule(pygame.KEYUP)
def handleInput(event):
    try: gamerules.bindings[event.key](False)
    except KeyError: pass
    
@gamerules.rule(pygame.MOUSEBUTTONDOWN)
def handleMouseDown(event):
    try: gamerules.mbindings[event.button](True,event.pos)
    except KeyError: pass

@gamerules.rule(pygame.MOUSEBUTTONUP)
def handleMouseUp(event):
    try: gamerules.mbindings[event.button](False,event.pos)
    except KeyError: pass

#@gamerules.rule(pygame.MOUSEMOTION)
def handleMouseMove(event):
    gamerules.mbindings[4](event.pos) #TODO: fix mouse button binding

@gamerules.rule(pygame.QUIT)
def quitGame(event):
    game.quit()

the_engine.message = ''
the_engine.messaging = False

@gamerules.rule(pygame.KEYDOWN)
def sendMessage(event):
    if not the_engine.messaging: return
    if event.key == pygame.K_RETURN:
        print 'a message:',the_engine.message
        the_engine.message = ''
        the_engine.messaging = False
    else: the_engine.message += chr(event.key)

def startMessage(tf):
    the_engine.messaging = True

def mbdown(tf,pos):
    print pos

def mmove(pos):
    print 'moving: ',pos

'''gamerules.bindings[pygame.K_d] = curry(commandGiver,PlayerChar.CMD_RIGHT)
gamerules.bindings[pygame.K_a] = curry(commandGiver,PlayerChar.CMD_LEFT)
gamerules.bindings[pygame.K_w] = curry(commandGiver,PlayerChar.CMD_UP)
gamerules.bindings[pygame.K_s] = curry(commandGiver,PlayerChar.CMD_DOWN)'''
#gamerules.bindings[303] = curry(commandGiver,PlayerChar.CMD_UP)
#gamerules.bindings[303] = curry(commandGiver,PlayerChar.CMD_DOWN)
gamerules.mbindings[1] = select
gamerules.mbindings[3] = order
gamerules.bindings[pygame.K_m] = startMessage
#gamerules.bindings[4] = mmove

@gamerules.rule('collision')
def collision(force):
    print 'something collided', force

the_engine.run()



